/**
 *
 *

 *
 */
package bbd;
/*
 * Copyright 2007 microneering, Inc and James Gamber
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * 
 * BBD/bbd/BBDBeanArrayList.java
 */
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
 * @author james gamber
 *
 */
@SuppressWarnings("serial")
public class BBDBeanArrayList<E> extends ArrayList<E>
        implements BBDColumn {
    
    final static private Logger log = Logger
            .getLogger(BBDBeanArrayList.class.getName());
    
    final protected ArrayList<String> columnNames = new ArrayList<String>();
    
    final protected ArrayList<Integer> columnTypes = new ArrayList<Integer>();
    
    /** Creates a new instance of BBDBeanList */
    public BBDBeanArrayList() {
        super();
        
    }
    
    /**
     * Constructor from JDBC result set.
     *
     * @param rs
     *            JDBC resultSet
     * @param storedProcedure
     *            API for stored procedure
     * @throws SQLException
     */
    @SuppressWarnings("unchecked")
	public BBDBeanArrayList(final ResultSet rs, final BBDBeanAPI<E> storedProcedure)
            throws SQLException {
        super();
        
        final ResultSetMetaData rsmd = rs.getMetaData();
        setMetaData(rsmd);
        final int cols = rsmd.getColumnCount();
        
        // User reflection setter to set bean properties
        final Method[] methods = storedProcedure.getRowClass().getMethods();
        final Method[] setMethods = getSetMethods(methods);
        
        if (setMethods.length < cols) {
        	String errMsg = "There are "+setMethods.length+" public set methods, but the result set returned "+cols+" values per row!";
        	log.severe(errMsg);
        	throw new IllegalArgumentException(errMsg);
        }
        
      
        while (rs.next()) {
            
            E bean = null;
            final Class<E> c = (Class<E>)storedProcedure.getRowClass();
            
            try {
                bean = c.newInstance();
            } catch (final InstantiationException e) {
                log.severe(e.toString());
                return;
            } catch (final IllegalAccessException e) {
                log.severe(e.toString());
                return;
            }
            
            boolean foundSetMethod = false;
            
            for (int i = 1; i <= cols; i++) {
                
                final String columnTitle = getTitle(i-1);
                
                for (final Method element : methods) {
                    if (element.getName().equalsIgnoreCase(
                            "set" + columnTitle)) {
                        try {
                            element.invoke(bean, rs.getObject(i));
                            foundSetMethod = true;
                            break;
                        } catch (final IllegalArgumentException e) {
                            log.severe(e.toString());
                        } catch (final IllegalAccessException e) {
                            log.severe(e.toString());
                        } catch (final InvocationTargetException e) {
                            log.severe(e.toString());
                        }
                    }
                }
                
                /**
                 * If the setter method is not found by name, then use setter by position.
                 * This is a bit risky.  It requires that the bean setters methods are in the
                 * same sequence in the bean class as the fields of the result set.
                 * 
                 * Databases have unknown column order for 'select * from table1'.
                 * 
                 * It is best to return values in a result set with specific name values that match
                 * the bean setter method names.
                 * 
                 * The BBD JPA implementation enforces this be generating stored procedures that use
                 * the bean property names.
                 */
                if (!foundSetMethod) {
                    try {
                        setMethods[i-1].invoke(bean, rs.getObject(i));
                    } catch (final IllegalArgumentException e) {
                        log.severe(e.toString());
                    } catch (final IllegalAccessException e) {
                        log.severe(e.toString());
                    } catch (final InvocationTargetException e) {
                        log.severe(e.toString());
                    }
                }
                
            }
            
            this.add(bean);
            
        }
    }
    
    private Method[] getSetMethods(final Method[] methods) {
        final ArrayList<Method> al = new ArrayList<Method>();
        
        for (final Method method : methods) {
            if (method.getName().startsWith("set")) {
				al.add(method);
			}
        }
        
        return al.toArray(new Method[0]);
    }
    
    /**
     *
     * @param rsmd
     * @throws java.sql.SQLException
     */
    public void setMetaData(final ResultSetMetaData rsmd) throws SQLException {
        columnNames.clear();
       
        final int cols = rsmd.getColumnCount();
        for (int i = 1; i <= cols; i++) {
            columnNames.add(rsmd.getColumnLabel(i));
            columnTypes.add(rsmd.getColumnType(i));
            log.info("Query result set meta data: column name ["
                    + columnNames.get(i - 1) + "]: sql type ["
                    + columnTypes.get(i - 1) + "]");
        }
        
    }
    
    public String getTitle(final int i) {
        return columnNames.get(i);
    }
    
    public String[] getTitle() {
        return columnNames.toArray(new String[0]);
    }
    
    public String getColumnName(final int index) {
        
        if (index < columnNames.size()) {
            return columnNames.get(index);
        } else {
            log.severe("Column does not exist at index " + index);
        }
        
        return null;
    }
    
        /*
         * (non-Javadoc)
         *
         * @see bbd.BBDColumn#getSqlType()
         */
    public int getSqlType(final int index) {
        
        if (index < columnTypes.size()) {
            return columnTypes.get(index);
        } else {
            log.severe("Column type does not exist at index " + index);
        }
        
        return -1;
        
    }
    
}
